#include <avr/io.h>
#include <avr/pgmspace.h>
#include <string.h>
#include "TimeDate.h"
#include "Globals.h"
#include "DisplayOutput.h"
#include "DisplayControl.h"
#include "ButtonControl.h"
#include "TimeDate.h"
#include "menu.h"

#define MENU_DISP_ILLUMINATION_TIME_U8 ((uint8_t) (6U))
#define MENU_INACTIVITY_TIME_U8        ((uint8_t) (60U))

typedef enum
{
  Menu_Clock = 0,
  Menu_Alarm,
  Menu_Alarm_Selection,
  Menu_Alarm_Active_Set,
  Menu_Alarm_Hour_Set,
  Menu_Alarm_Min_Set,
  Menu_Alarm_Day_Set,
  Menu_Alarm_Light_Set,
  Menu_Alarm_Sound_Set,
  Menu_Alarm_Sleep_Set,
  Menu_Config,
  Menu_MAX,
  Menu_Invalid
}T_MenuEntry;

void MenuButtonNotifyEnter( T_ButtonReason iReason_t );
void MenuButtonNotifyBack( T_ButtonReason iReason_t );
void MenuButtonNotifyUp( T_ButtonReason iReason_t );
void MenuButtonNotifyDown( T_ButtonReason iReason_t );
void MenuButtonNotifyCap( T_ButtonReason iReason_t );
void MenuTimeNotifySecond( void );
void MenuButtonWatchdog( T_ButtonReason iReason_t );

typedef struct _T_Menu {
  const char*   text;                 /* Test of the selected menu */
  int8_t        parent;               /* Menu number of parent menu */
  int8_t        first_son;            /* Menu number of first son */
  int8_t        up;                   /* Menu number of next sibling up */
  int8_t        down;                 /* Menu number of previous sibling up */
  uint8_t       ( *checked ) (void);  /* flag to indicate if the control is on menu or menu entry side */
  T_Bool        ( *fp )( T_MenuButton );      /* function pointer for the menu entry */
} T_Menu;

typedef struct _T_MenuEvents
{
  uint8_t TimeSinceActivitySec_u8;
  uint8_t BlinkState  :2; 
  uint8_t ButtonCap   :1;
  T_MenuButton Button;
} T_MenuEvents;

typedef struct _T_CurMenu_t
{
  T_MenuEntry Entry;  /* enumeration of the current entry item  */
  T_Menu      Menu;        /* ram copy of the current menu item.     */
  uint8_t     MenuChecked_u8;
}T_CurMenu_t;

//static T_MenuEntry gCurMenuEntry_t = Menu_Clock;
static T_CurMenu_t gCurMenu_t = { .Entry  = Menu_Clock, .MenuChecked_u8 = 1 };
static T_MenuEvents gMenuEvents_t = { .Button.Enter = 0, .Button.Up = 0, .Button.Down = 0, .Button.Back = 0, .TimeSinceActivitySec_u8 = 0 };

static const char Clock_str[]             = "Clock\0";
static const char Alarm_str[]             = "Alarm\0";
static const char Config_str[]            = "Configuration\0";
static const char Alarm_Selection_str[]   = "Alarm selection\0";
static const char Alarm_Active_str[]      = "Alarm Activation\0";
static const char Alarm_Hour_str[]        = "Alarm Hour\0";
static const char Alarm_Min_str[]         = "Alarm Min\0";
static const char Alarm_Day_str[]         = "Alarm Day\0";
static const char Alarm_Light_str[]       = "Alarm Light\0";
static const char Alarm_Sound_str[]       = "Alarm Sound\0";
static const char Alarm_Sleep_str[]       = "Alarm Sleep\0";

static const T_Menu gMenu_t[Menu_MAX] PROGMEM =
{
  /* Root */
  { .text = Clock_str,            .parent = Menu_Invalid,       .first_son = Menu_Invalid,          .up = Menu_Config,            .down = Menu_Alarm,               .checked = NULL, .fp = DsoWriteMainView },
  /* Alarm */
  { .text = Alarm_str,            .parent = Menu_Invalid,       .first_son = Menu_Alarm_Selection,  .up = Menu_Clock,             .down = Menu_Config,              .checked = NULL, .fp = NULL },
  /* Sons of Alarm */
  { .text = Alarm_Selection_str,  .parent = Menu_Alarm,         .first_son = Menu_Invalid,          .up = Menu_Alarm_Sleep_Set,   .down = Menu_Alarm_Active_Set,    .checked = NULL, .fp = NULL },
  { .text = Alarm_Active_str,     .parent = Menu_Alarm,         .first_son = Menu_Invalid,          .up = Menu_Alarm_Selection,   .down = Menu_Alarm_Hour_Set,      .checked = NULL, .fp = NULL },
  { .text = Alarm_Hour_str,       .parent = Menu_Alarm,         .first_son = Menu_Invalid,          .up = Menu_Alarm_Active_Set,  .down = Menu_Alarm_Min_Set,       .checked = NULL, .fp = NULL },
  { .text = Alarm_Min_str,        .parent = Menu_Alarm,         .first_son = Menu_Invalid,          .up = Menu_Alarm_Hour_Set,    .down = Menu_Alarm_Day_Set,       .checked = NULL, .fp = NULL },
  { .text = Alarm_Day_str,        .parent = Menu_Alarm,         .first_son = Menu_Invalid,          .up = Menu_Alarm_Min_Set,     .down = Menu_Alarm_Light_Set,     .checked = NULL, .fp = NULL },
  { .text = Alarm_Light_str,      .parent = Menu_Alarm,         .first_son = Menu_Invalid,          .up = Menu_Alarm_Day_Set,     .down = Menu_Alarm_Sound_Set,     .checked = NULL, .fp = NULL },
  { .text = Alarm_Sound_str,      .parent = Menu_Alarm,         .first_son = Menu_Invalid,          .up = Menu_Alarm_Light_Set,   .down = Menu_Alarm_Sleep_Set,     .checked = NULL, .fp = NULL },
  { .text = Alarm_Sleep_str,      .parent = Menu_Alarm,         .first_son = Menu_Invalid,          .up = Menu_Alarm_Sound_Set,   .down = Menu_Alarm_Selection,     .checked = NULL, .fp = NULL },
  /* Configuration */
  { .text = Config_str,           .parent = Menu_Invalid,       .first_son = Menu_Invalid,          .up = Menu_Alarm,             .down = Menu_Alarm,               .checked = NULL, .fp = NULL }
};

void MenuInit( void )
{
  /* Button notifications */
  BcoRegisterNotification( MenuButtonNotifyEnter, Button_Low2High,    Button_Enter );
  BcoRegisterNotification( MenuButtonNotifyBack,  Button_Low2High,    Button_Back );
  BcoRegisterNotification( MenuButtonNotifyUp,    Button_Low2High,    Button_Left );
  BcoRegisterNotification( MenuButtonNotifyDown,  Button_Low2High,    Button_Right );
  BcoRegisterNotification( MenuButtonNotifyCap,   Button_Low2High,    Button_Cap );
  BcoRegisterNotification( MenuButtonWatchdog,    Button_AllReasons,  Button_All );
  /* Timer notification */
  TidRegisterNotification( MenuTimeNotifySecond, SecondChange );
  gCurMenu_t.Entry           = Menu_Clock;
  //gCurMenu_t.Entry          = Menu_Clock;
  gCurMenu_t.MenuChecked_u8 = 1;
}

void MenuCyclic( void )
{
  static T_MenuEntry lLastMenuEntry_t = Menu_Invalid;

  if( lLastMenuEntry_t != gCurMenu_t.Entry )
  {
    memcpy_P(&gCurMenu_t.Menu, &gMenu_t[gCurMenu_t.Entry], sizeof(gCurMenu_t.Menu)); // Get menu entry from flash
    MenuWriteMenu( gCurMenu_t.Menu.text, strlen(gCurMenu_t.Menu.text) );
    gCurMenu_t.MenuChecked_u8 = 0;
    lLastMenuEntry_t = gCurMenu_t.Entry;
  }

  if( 0 != gCurMenu_t.MenuChecked_u8 )
  {
    if( NULL != gCurMenu_t.Menu.fp )
    {
      gCurMenu_t.MenuChecked_u8 = gCurMenu_t.Menu.fp(gMenuEvents_t.Button);
    }
  }
  else
  {
    if( 0 != gMenuEvents_t.Button.Down )
    {
      gCurMenu_t.Entry = gCurMenu_t.Menu.down;
    }
    if( 0 != gMenuEvents_t.Button.Up )
    {
      gCurMenu_t.Entry = gCurMenu_t.Menu.up;
    }
    if( 0 != gMenuEvents_t.Button.Enter )
    {
      if( Menu_Invalid != gCurMenu_t.Menu.first_son )
      {
        gCurMenu_t.Entry = gCurMenu_t.Menu.first_son;
      }
      else
      {
        gCurMenu_t.MenuChecked_u8 = 1;
      }
    }
  }
  if( 0 != gMenuEvents_t.Button.Back )
  {
      gCurMenu_t.Entry = gCurMenu_t.Menu.parent;
  }

  gMenuEvents_t.Button.Down   = 0;
  gMenuEvents_t.Button.Up     = 0;
  gMenuEvents_t.Button.Enter  = 0;
  gMenuEvents_t.Button.Back   = 0 ;
}

void MenuButtonNotifyEnter( T_ButtonReason iReason_t )
{
  gMenuEvents_t.Button.Enter = 1;
}

void MenuButtonNotifyBack( T_ButtonReason iReason_t )
{
  gMenuEvents_t.Button.Back = 1;
}

void MenuButtonNotifyUp( T_ButtonReason iReason_t )
{
  gMenuEvents_t.Button.Up = 1;
}

void MenuButtonNotifyDown( T_ButtonReason iReason_t )
{
  gMenuEvents_t.Button.Down = 1;
}

void MenuButtonNotifyCap( T_ButtonReason iReason_t )
{
  gMenuEvents_t.ButtonCap = 1;
}

void MenuTimeNotifySecond( void )
{
  /* activity monitoring */
  if( 0 < gMenuEvents_t.TimeSinceActivitySec_u8 )
  {
    --gMenuEvents_t.TimeSinceActivitySec_u8;
    /* illumination monitoring */
    if( (MENU_INACTIVITY_TIME_U8 - MENU_DISP_ILLUMINATION_TIME_U8) == gMenuEvents_t.TimeSinceActivitySec_u8 )
    {
      DispSetState( DispOff, DispSmooth );
    }
    /* menu monitoring */
    if( 0 == gMenuEvents_t.TimeSinceActivitySec_u8 )
    {
      /* fallback to clock */
      gCurMenu_t.Entry = Menu_Clock;
    }
  }
}

void MenuButtonWatchdog( T_ButtonReason iReason_t )
{
  /* set display alive Time */
  gMenuEvents_t.TimeSinceActivitySec_u8  = MENU_INACTIVITY_TIME_U8;
  DispSetState( DispOn, DispSmooth );
}


